from __future__ import with_statement

import transaction
import time
from crv.model import DBSession, metadata
from crv.model.crvmodel import *

import logging
log = logging.getLogger(__name__)

from threading import Thread, Lock
from collections import deque

from crv.tools.singleton import Singleton
from crv.tools.statemachinemgr import StateMachineMgr

def checkState_INITIALIZED_Cluster(cluster):
    #DBSession.expire_Cluster(cluster)
    for irole in cluster.role:
        if irole.getAttr("enabled")=="True":
            if irole.currentSC!=Role.SC.SC_INITIALIZED:
                return False
    return True

def checkState_STARTING_Cluster(cluster):
    #DBSession.expire_Cluster(cluster)
    return 'STARTING'==checkState_STOPPINGorSTARTING_Cluster(cluster)

def checkState_STARTED_Cluster(cluster):
    #DBSession.expire_Cluster(cluster)
    for irole in cluster.role:
        if irole.getAttr("enabled")=="True":
            if irole.currentSC!=Role.SC.SC_STARTED:
                return False
    return True

def checkState_STOPPINGorSTARTING_Cluster(cluster):
    nEnabled=0
    nStarted=0
    nStopped=0
    nRemoving=0
    nApplying=0
    nStartingVM=0
    nStoppingVM=0
    for irole in cluster.role:
        #log.debug("irole.currentSC=%s"%Role.SCname[irole.currentSC])
        if irole.getAttr("enabled")=="True":
            nEnabled=nEnabled+1
            if irole.currentSC==Role.SC.SC_STARTED:
                nStarted=nStarted+1
            elif irole.currentSC==Role.SC.SC_STOPPED:
                nStopped=nStopped+1
            elif irole.currentSC==Role.SC.SC_REMOVING:
                nRemoving=nRemoving+1
            elif irole.currentSC==Role.SC.SC_APPLYING:
                nApplying=nApplying+1
            elif irole.currentSC==Role.SC.SC_STARTINGVM:
                nStartingVM=nStartingVM+1
            elif irole.currentSC==Role.SC.SC_STOPPINGVM:
                nStoppingVM=nStoppingVM+1
                
            if irole.currentSC!=Role.SC.SC_STOPPED and irole.currentSC!=Role.SC.SC_REMOVING and irole.currentSC!=Role.SC.SC_APPLYING and irole.currentSC!=Role.SC.SC_STARTED and irole.currentSC!=Role.SC.SC_STARTINGVM and irole.currentSC!=Role.SC.SC_STOPPINGVM:
                #if there is a role that is neither Stopped nore Removing, this is not a STOPPING nor STARTING state for the cluster
                #log.debug("Returning NEITHER")
                return "NEITHER"
            
    #log.debug("nEnabled=%d, nStarted=%d, nStopped=%d, nRemoving=%d, nApplying=%d"%(nEnabled,nStarted,nStopped,nRemoving,nApplying))
    if nEnabled==nStarted or nEnabled==nStopped:
        #this cluster should have a stopped state or started state
        #log.debug("Returning NEITHER")
        return "NEITHER"
    
    if nRemoving==0 and nApplying==0 and nStoppingVM==0 and nStartingVM==0 or (nRemoving>0 or nStoppingVM>0 and nApplying>0 or nStartingVM>0):
        
        #only started and stopped, roles, need to checktargetSC.
        #both starting and stopping can have only "STARTED" or "STOPPED" roles, so use the target the tell the difference
        #targetSC=STARTED => state is STARTING, targetSC=STOPPED => state is STOPPING
        if cluster.targetSC==cluster.SC.SC_STARTED:
            #log.debug("Returning STARTING")
            return "STARTING"
        elif cluster.targetSC==cluster.SC.SC_STOPPED:
            #log.debug("Returning STOPPING")
            return "STOPPING"
        else:
            raise RuntimeError("Unsupported targetSC")
        
    elif (nRemoving>0 or nStoppingVM>0) and (nApplying==0 and nStartingVM==0):
        #log.debug("Returning STOPPING")
        return "STOPPING"
    elif (nRemoving==0 and nStoppingVM==0) and (nApplying>0 or nStartingVM>0):
        #log.debug("Returning STOPPING")
        return "STARTING"
            
    raise RuntimeError("Shouldn't be here ")

def checkState_STOPPING_Cluster(cluster):
    #DBSession.expire_Cluster(cluster)
    return 'STOPPING'==checkState_STOPPINGorSTARTING_Cluster(cluster)

def checkState_EXCEPTION_Cluster(cluster):
    #DBSession.expire_Cluster(cluster)
    hasException=False
    for irole in cluster.role:
        if irole.getAttr("enabled")=="True":
            if irole.currentSC==Role.SC.SC_FIXING:
                #if there is a fixing state, it should be fixing
                return False
            if irole.currentSC==Role.SC.SC_EXCEPTION:
                hasException=True
    if hasException:
        return True
    return False
    
def checkState_FIXING_Cluster(cluster):
    #DBSession.expire_Cluster(cluster)
    
    for irole in cluster.role:
        if irole.getAttr("enabled")=="True":
            if irole.currentSC==Role.SC.SC_FIXING:
                return True
    return False
    
    
#canAdvance is only needed for those state transitions that need to control its children, e.g. INITIALIZED->STARTING, need to loop over roles and set targetSC=STARTED
#setting to exception states are automatic

def canAdvance_INITIALIZED_STARTING_Cluster(cluster):
    #if it's already in initialized, it should be able to start right away
    return True

def doAdvance_INITIALIZED_STARTING_Cluster(cluster):
    #need to loop over roles and set targetSC=STARTED
    for irole in cluster.role:
        #log.debug("ClusterMgr::doAdvance_INITIALIZED_STARTING:: setting the targetSC  of irole=%s from %s to %s"%(Role.SCname[irole.targetSC],irole.name,Role.SCname[Role.SC.SC_STARTED]))
        if irole.getAttr("enabled")=="True":
            irole.targetSC=Role.SC.SC_STARTED

def canAdvance_STARTED_STOPPING_Cluster(cluster):
    return True

def doAdvance_STARTED_STOPPING_Cluster(cluster):
    #need to loop over roles and set targetSC=STARTED
    for irole in cluster.role:
        if irole.getAttr("enabled")=="True":
            irole.targetSC=Role.SC.SC_STOPPED


class ClusterMgr(Singleton):
    """
    @author Yushu Yao
    @version 0.1
    """

    def __init__(self):
        super(ClusterMgr, self).__init__()
        self.__smgr = StateMachineMgr()
        
        self.__smgr.addNodes(Cluster.SCname.keys())
        self.__smgr.addEdge(Cluster.SC.SC_INITIALIZED, Cluster.SC.SC_STARTING)
        self.__smgr.addEdge(Cluster.SC.SC_STARTING, Cluster.SC.SC_STARTED)
        self.__smgr.addEdge(Cluster.SC.SC_STARTING, Cluster.SC.SC_EXCEPTION)
        self.__smgr.addEdge(Cluster.SC.SC_STARTED, Cluster.SC.SC_STOPPING)
        self.__smgr.addEdge(Cluster.SC.SC_STARTED, Cluster.SC.SC_EXCEPTION)
        self.__smgr.addEdge(Cluster.SC.SC_STOPPING, Cluster.SC.SC_INITIALIZED)
        self.__smgr.addEdge(Cluster.SC.SC_STOPPING, Cluster.SC.SC_EXCEPTION)
        self.__smgr.addEdge(Cluster.SC.SC_EXCEPTION, Cluster.SC.SC_FIXING)
        self.__smgr.addEdge(Cluster.SC.SC_FIXING, Cluster.SC.SC_INITIALIZED)

        self.__smgr.setFunc_checkState(Cluster.SC.SC_INITIALIZED,checkState_INITIALIZED_Cluster)
        self.__smgr.setFunc_checkState(Cluster.SC.SC_STARTING , checkState_STARTING_Cluster )
        self.__smgr.setFunc_checkState(Cluster.SC.SC_STARTED , checkState_STARTED_Cluster )
        self.__smgr.setFunc_checkState(Cluster.SC.SC_STOPPING , checkState_STOPPING_Cluster )
        self.__smgr.setFunc_checkState(Cluster.SC.SC_EXCEPTION , checkState_EXCEPTION_Cluster )
        self.__smgr.setFunc_checkState(Cluster.SC.SC_FIXING , checkState_FIXING_Cluster )

        self.__smgr.setFunc_canAdvance(Cluster.SC.SC_INITIALIZED,Cluster.SC.SC_STARTING,canAdvance_INITIALIZED_STARTING_Cluster)
        self.__smgr.setFunc_doAdvance(Cluster.SC.SC_INITIALIZED,Cluster.SC.SC_STARTING,doAdvance_INITIALIZED_STARTING_Cluster)
        self.__smgr.setFunc_canAdvance(Cluster.SC.SC_STARTED,Cluster.SC.SC_STOPPING,canAdvance_STARTED_STOPPING_Cluster)
        self.__smgr.setFunc_doAdvance(Cluster.SC.SC_STARTED,Cluster.SC.SC_STOPPING,doAdvance_STARTED_STOPPING_Cluster)
        
        self.__smgr.initialize(Cluster.SC, Cluster.SCname)
        
    def advanceStates(self,obj):
        return self.__smgr.advanceStates(obj)
        
